/*
 * Copyright 2017, Data61, CSIRO (ABN 41 687 119 230)
 *
 * SPDX-License-Identifier: BSD-2-Clause
 */

#include <autoconf.h>
#include <sync/recursive_mutex.h>
#include <stddef.h>
#include <assert.h>
#include <limits.h>

#include <sel4/sel4.h>
#ifdef CONFIG_DEBUG_BUILD
#include <sel4debug/debug.h>
#endif

static void *thread_id(void)
{
    return (void *)seL4_GetIPCBuffer();
}

int sync_recursive_mutex_init(sync_recursive_mutex_t *mutex, seL4_CPtr notification)
{
    if (mutex == NULL) {
        ZF_LOGE("Mutex passed to sync_recursive_mutex_init is NULL");
        return -1;
    }
#ifdef CONFIG_DEBUG_BUILD
    /* Check the cap actually is a notification. */
    assert(debug_cap_is_notification(notification));
#endif

    mutex->notification.cptr = notification;
    mutex->owner = NULL;
    mutex->held = 0;

    /* Prime the endpoint. */
    seL4_Signal(mutex->notification.cptr);
    return 0;
}

int sync_recursive_mutex_lock(sync_recursive_mutex_t *mutex)
{
    if (mutex == NULL) {
        ZF_LOGE("Mutex passed to sync_recursive_mutex_lock is NULL");
        return -1;
    }
    if (thread_id() != mutex->owner) {
        /* We don't already have the mutex. */
        seL4_Wait(mutex->notification.cptr, NULL);
        __atomic_thread_fence(__ATOMIC_ACQUIRE);
        assert(mutex->owner == NULL);
        mutex->owner = thread_id();
        assert(mutex->held == 0);
    }
    if (mutex->held == UINT_MAX) {
        /* We would overflow if we re-acquired the mutex. Note that we can only
         * be in this branch if we already held the mutex before entering this
         * function, so we don't need to release the mutex here.
         */
        return -1;
    }
    mutex->held++;
    return 0;
}

int sync_recursive_mutex_unlock(sync_recursive_mutex_t *mutex)
{
    if (mutex == NULL) {
        ZF_LOGE("Mutex passed to sync_recursive_mutex_lock is NULL");
        return -1;
    }
    assert(mutex->owner == thread_id());
    assert(mutex->held > 0);
    mutex->held--;
    if (mutex->held == 0) {
        /* This was the outermost lock we held. Wake the next person up. */
        __atomic_store_n(&mutex->owner, NULL, __ATOMIC_RELEASE);
        seL4_Signal(mutex->notification.cptr);
    }
    return 0;
}

int sync_recursive_mutex_new(vka_t *vka, sync_recursive_mutex_t *mutex)
{
    int error = vka_alloc_notification(vka, &(mutex->notification));

    if (error != 0) {
        return error;
    } else {
        return sync_recursive_mutex_init(mutex, mutex->notification.cptr);
    }
}

int sync_recursive_mutex_destroy(vka_t *vka, sync_recursive_mutex_t *mutex)
{
    vka_free_object(vka, &(mutex->notification));
    return 0;
}
